package cs536.codegen.mips;

import java.util.List;
import java.util.Iterator;

import cs536.ast.SourceLocation;
import cs536.ast.AstVisitor;
import static cs536.ast.AbstractSyntaxTree.*;
import cs536.ast.AbstractSyntaxTree;
import cs536.staticSemantics.symbolTable.LocalSymbol;
import cs536.staticSemantics.symbolTable.VariableSymbol;
import cs536.staticSemantics.types.*;


import cs536.codegen.*;

public class CodeGenerator extends AstVisitor<MipsChunk>
{
    Mips mips = new Mips();
    boolean mainError = false;
    boolean mainVisited = false;
    boolean returnReached = false;
    
    // i use these fields for return statements
    int currSizeLocals;
    int currSizeArgs;
    
    // label generators
	// need label generator to generate unique labels
	LabelGenerator startWhile = new LabelGenerator("StartWhile");
	LabelGenerator endWhile = new LabelGenerator("EndWhile");
	LabelGenerator falseLabel = new LabelGenerator("FalseLabel");

    public MipsChunk visitAst(AbstractSyntaxTree ast, List<MipsChunk> decls)
    {
    	MipsChunk chunk = new MipsChunk();

    	for (MipsChunk temp : decls) {
    		chunk.appendChunk(temp);
    	}

    	// check for main related errors
    	if (mainError) {
    		return new MipsChunk();
    	} else if (!mainVisited) {
    		System.out.println("0:0:no main function");
    		return new MipsChunk();
    	} else {
    		return chunk;
    	}
    }
    
    public MipsChunk visitGlobalVarDecl(GlobalVarDecl globalVar,
                                      MipsChunk typeResult, MipsChunk exprResult)
    {
    	int space;
    	if (globalVar.getTypeNode().asSemanticType() == IntType.make()) {
    		space = 4;
    	} else if (globalVar.getTypeNode().asSemanticType() == DoubleType.make()) {
    		space = 8;
    	} else {
    		space = 4;
    	}
    	
    	MipsChunk chunk = new MipsChunk();
    	
    	chunk.append(mips._data());
    	chunk.append(mips._align(2));
    	chunk.append(mips.label("_" + globalVar.getName() + ":     " + ".space " + space));
    	//chunk.append(mips._space(space));
    	
    	// initialize if there is an expr result
    	if (exprResult != null) {
    		chunk.appendChunk(exprResult);
    		chunk.popToReg(Regs.t0);
    		chunk.append(mips.la(Regs.t1, "_" + globalVar.getName()));
    		chunk.append(mips.sw(Regs.t0, Regs.t1));
    	}
        
    	return chunk;
    }

    public MipsChunk visitFuncDecl(FuncDecl funcDecl, MipsChunk typeResult,
                                 List<MipsChunk> formalResults, List<MipsChunk> statementResults)
    {
    	this.currSizeLocals = funcDecl.getSizeOfLocals();
    	this.currSizeArgs = funcDecl.getSizeOfParams();
    	this.returnReached = false;
    	
    	MipsChunk chunk = new MipsChunk();
    	
        //the function �preamble�
    	chunk.append(mips._text());
    	// check to see if function is main or not
    	if (funcDecl.getName().equals("main")) {
    		// check to see that function is both void and has no arguments
    		if ((funcDecl.getFormalArguments().size() != 0 ) && !mainError) {
    			mainError = true;
    			System.out.println(funcDecl.getLocation().getRow() + ":" +
    								funcDecl.getLocation().getCol() + ":"
    								+ "main must take no arguments");
    		} else if (funcDecl.getFunctionSymbol().getType().getReturnType() != null && !mainError) {
    			mainError = true;
    			System.out.println(funcDecl.getLocation().getRow() + ":" +
						funcDecl.getLocation().getCol() + ":"
						+ "main must return void");
    		}
    		// end error checking
    		chunk.append(mips._globl(funcDecl.getName()));
    		chunk.append(mips.label(funcDecl.getName()));
    		mainVisited = true;
    	} else {
    		chunk.append(mips.label("_" + funcDecl.getName()));
    	}

        //the function entry (to set up the function�s Activation Record)

    	// # (1) push return addr
    	// first we make space for 4 bytes, then store the address on the stack
        chunk.append(mips.subu(Regs.sp, Regs.sp, 4));
        chunk.append(mips.sw(Regs.ra, 0, Regs.sp));
        // # (2) "push" space for locals
        
        /*
         * This will be tricky, we have to find out the total size of the local variables
         * formalResults.size() just returns the number of formal arguments...
         * we'll have to compute the total size of local variables in name analysis
         */
        chunk.append(mips.subu(Regs.sp, Regs.sp, funcDecl.getSizeOfLocals()), 
        		"Uses \"getSizeOfLocals\" method"); //...I think
        
        // # (3) push control link
        chunk.append(mips.subu(Regs.sp, Regs.sp, 4));        
        chunk.append(mips.sw(Regs.fp, 0, Regs.sp)); //...I think
        
        // # (4) set the FP
        // # note: the following sets the FP to point to first local.
        // #       The reason for "+ 4" is 4 bytes for the saved FP
        // 
        // addu $fp, $sp, 4
        chunk.append(mips.addu(Regs.fp, Regs.sp, 4), "Sets FP to point to first local");    
        
        // append statement results (return statements will presumably be here)
        for (MipsChunk temp : statementResults) {
        	chunk.appendChunk(temp);
        }
        
        // function exits taken care of in returnStmt
        // .. unless there is no return statement of course.
        if (!this.returnReached) {
            chunk.append(mips.lw(Regs.ra, this.currSizeLocals, Regs.fp)); // load return address
            chunk.append(mips.move(Regs.t0, Regs.fp));
            chunk.append(mips.lw(Regs.fp, -4, Regs.fp));
            chunk.append(mips.add(Regs.sp, Regs.sp, this.currSizeLocals + this.currSizeArgs + 8));
            chunk.append(mips.jr(Regs.ra));
        }
        
    	return chunk;

    }

    public MipsChunk visitFormalArg(FormalArg formalArg, MipsChunk typeResult)
    {
        return typeResult;
    }

    public MipsChunk visitVarDeclStmt(VarDeclStmt localVar,
                                    MipsChunk typeResult, MipsChunk initResult)
    {
    	MipsChunk chunk = new MipsChunk();
    	int offset = localVar.getLocalSymbol().getOffset();
    	if (initResult != null) {
    		chunk.appendChunk(initResult);
    		chunk.popToReg(Regs.t0);
    		chunk.append(mips.sw(Regs.t0, offset, Regs.fp));
    		return chunk;
    	} else {
    		return chunk;
    	}
    }

    public MipsChunk visitReturnStmt(ReturnStmt retStmt, MipsChunk valResult)
    {
    	this.returnReached = true;
    	MipsChunk chunk = new MipsChunk();
        //function exit (restoring the stack, and returning to the caller).
        // lw   $ra, <size of locals>($fp) # load return address
        // move $t0, $fp                   # save control link
        // lw   $fp, -4($fp)               # restore FP
        // add  $sp, $sp, <size of locals + size of params + 8> # restore SP to just below the parameters
        // jr   $ra                        # return\
        chunk.append(mips.lw(Regs.ra, this.currSizeLocals, Regs.fp)); // load return address
        chunk.append(mips.move(Regs.t0, Regs.fp));
        chunk.append(mips.lw(Regs.fp, -4, Regs.fp));
        chunk.append(mips.add(Regs.sp, Regs.sp, this.currSizeLocals + this.currSizeArgs + 8));
        
    	if (valResult != null) {
    		chunk.appendChunk(valResult);
    		// result will be left on top of stack
    	}
    	chunk.append(mips.jr(Regs.ra));
        return chunk;
    }

    public MipsChunk visitIfStmt(IfStmt ifStmt, MipsChunk conditionResult,
                               List<MipsChunk> thenResults, List<MipsChunk> elseResults)
    {
    	//MipsChunk temp;
    	String label = falseLabel.genLabel();
    	String endLabel = falseLabel.genLabel();
    	
    	MipsChunk chunk = new MipsChunk();
    	chunk.appendChunk(conditionResult);
    	chunk.popToReg(Regs.t0);
    	chunk.append(mips.beqz(Regs.t0, label));
    	
    	//True    	
    	for (MipsChunk temp: thenResults) {
    		chunk.appendChunk(temp);
    	}
    	chunk.append(mips.j(endLabel));
    	
    	//False
    	chunk.append(mips.label(label));
    	for (MipsChunk temp: elseResults) {
    		chunk.appendChunk(temp);
    	}
    	
    	// end
    	chunk.append(mips.label(endLabel));
    	
        return chunk;
    }

    public MipsChunk visitWhileStmt(WhileStmt whileStmt, MipsChunk conditionResult,
                                  List<MipsChunk> bodyResults)
    {	
    	String stWhile = startWhile.genLabel();
    	String edWhile = endWhile.genLabel();
    	MipsChunk chunk = new MipsChunk(); 
    	
    	chunk.append(mips.label(stWhile));
    	chunk.appendChunk(conditionResult);
    	chunk.popToReg(Regs.t0);
    	chunk.append(mips.beqz(Regs.t0, edWhile));
    	
    	for (MipsChunk temp: bodyResults) {
    		chunk.appendChunk(temp);
    	}
    	
    	chunk.append(mips.j(stWhile));
    	
    	chunk.append(mips.label(edWhile));
    	
    	
        return chunk;
    }

    public MipsChunk visitExpressionStmt(ExpressionStmt exprStmt, MipsChunk expResult)
    {
        return expResult;
    }

    public MipsChunk visitWriteStmt(WriteStmt writeStmt, MipsChunk valResult)
    {

    	MipsChunk chunk = new MipsChunk();

    	
    	//if(writeStmt.getExpression().getType() == IntType.make()) {
    		// step (1)
        	chunk.appendChunk(valResult);
        	// step (2)
        	chunk.popToReg(Regs.a0);
        	// step (3)
        	chunk.append(mips.li(Regs.v0, 1));
        	// step (4)
        	chunk.append(mips.syscall());
    	//}
    	
//    	if(writeStmt.getExpression().getType() == StringType.make()) {
//        	chunk.appendChunk(valResult);        	
//        	chunk.append(mips.li(Regs.v0, 4));
//        	chunk.append(mips.syscall());
//        	chunk.popToReg(Regs.f12);
//    	}
//    	
//    	if(writeStmt.getExpression().getType() == DoubleType.make()) {
//        	chunk.appendChunk(valResult);
//        	chunk.popToReg(Regs.a0);
//        	chunk.append(mips.li(Regs.v0, 3));
//        	chunk.append(mips.syscall());
//    	}    	

    	return chunk;
    }

    
    
    public MipsChunk visitReadStmt(ReadStmt readStmt)
    {
    	MipsChunk chunk = new MipsChunk();
    	// do the syscall
    	chunk.append(mips.li(Regs.v0, 5));
    	chunk.append(mips.syscall());
    	// perhaps a weird way of doing this, but whatever
    	String anString = readStmt.getDestinationSymbol().getAnnotationString();
    	if (anString.equals(":G")) {
    		// load word based on global label
    		chunk.append(mips.la(Regs.t1, "_" + readStmt.getDestinationSymbol().getName()));
    		chunk.pushReg(Regs.t1);
    	} else {
    		// load word based on offset of frame pointer
    		// we know we can cast because we're dealing with a local symbol
    		int offset = ((LocalSymbol) readStmt.getDestinationSymbol()).getOffset();
    		chunk.append(mips.la(Regs.v0, offset, Regs.fp));
    		chunk.pushReg(Regs.t1);
    	}
    	// address is in $t1... need to store
    	chunk.append(mips.sw(Regs.v0, Regs.t1));
        return chunk;
    }

    public MipsChunk visitFunctionCallExp(FunctionCallExp fcallExp,
                                        List<MipsChunk> argResults)
    {
    	// temp;
    	MipsChunk chunk = new MipsChunk();

        //Evaluate each actual parameter, pushing the values onto the stack;
    	for (MipsChunk temp : argResults) {
    		chunk.appendChunk(temp);
    	}

        //Jump and link (jump to the called function, leaving the return address in the RA register).
    	chunk.append(mips.jal(fcallExp.getName()));
    	
    	//Return value should be on top of stack   	
    	
    	return chunk;
        
    }

    public MipsChunk visitDerefOp(DerefOp derefOp, MipsChunk expResult)
    {
    	MipsChunk chunk = new MipsChunk();
    	chunk.appendChunk(expResult);
    	chunk.popToReg(Regs.t0);
    	chunk.append(mips.lw(Regs.t0, Regs.t0));
    	chunk.pushReg(Regs.t0);
        return chunk;
    }

    public MipsChunk visitNegationOp(NegationOp negationOp, MipsChunk expResult)
    {
        MipsChunk chunk = new MipsChunk();
        chunk.appendChunk(expResult);
        chunk.popToReg(Regs.t0);
        chunk.append(mips.mul(Regs.t0, Regs.t0, -1));
        chunk.pushReg(Regs.t0);
        return chunk;
    }

    public MipsChunk visitIncrementOp(IncrementOp incrOp, MipsChunk expResult)
    {
    	VarRefExp varRef = (VarRefExp)incrOp.getOperand();
    	MipsChunk chunk = new MipsChunk();  
    	
    	chunk.appendChunk(expResult);    	
    	chunk.popToReg(Regs.t1);  //value is $t1
    	chunk.append(mips.addi(Regs.t1, Regs.t1, 1));
    	chunk.append(mips.sw(Regs.t1, varRef.getName()));
    	
        return chunk;
    }

    public MipsChunk visitDecrementOp(DecrementOp decrOp, MipsChunk expResult)
    {
    	VarRefExp varRef = (VarRefExp)decrOp.getOperand();
    	MipsChunk chunk = new MipsChunk();  
    	
    	chunk.appendChunk(expResult);    	
    	chunk.popToReg(Regs.t1);  //value is $t1
    	chunk.append(mips.subi(Regs.t1, Regs.t1, 1));
    	chunk.append(mips.sw(Regs.t1, varRef.getName()));
    	
        return chunk;
    }

    public MipsChunk visitNotOp(NotOp logNotOp, MipsChunk expResult)
    {
    	String falseLab = falseLabel.genLabel();
    	String endLabel = falseLabel.genLabel();
    	MipsChunk chunk = new MipsChunk();
    	chunk.appendChunk(expResult);
    	chunk.popToReg(Regs.t0);
    	chunk.append(mips.beqz(Regs.t0, falseLab));
    	// false, switch to true
    	chunk.append(mips.li(Regs.t0, 0));
    	chunk.pushReg(Regs.t0);
    	chunk.append(mips.j(endLabel));
    	
    	// true switch to 0/false
    	chunk.append(mips.label(falseLab));
    	chunk.append(mips.li(Regs.t0, 1));
    	chunk.pushReg(Regs.t0);
    	
    	// the end label
    	chunk.append(mips.label(endLabel));
    	
        return chunk;
    }

    public MipsChunk visitAddrOfOp(AddrOfOp addrOfOp, MipsChunk expResult)
    {
    	MipsChunk chunk = new MipsChunk();
    	chunk.appendChunk(expResult);
    	chunk.popToReg(Regs.t0);
    	chunk.append(mips.la(Regs.t0, Regs.t0));
    	chunk.pushReg(Regs.t0);
        return chunk;
    }

    public MipsChunk visitAndOp(AndOp logAndOp, MipsChunk lhsResult, MipsChunk rhsResult)
    {
    	String oneFalse = falseLabel.genLabel();
    	String noneFalse = falseLabel.genLabel();
    	String endLabel = falseLabel.genLabel();
    	
    	MipsChunk chunk = new MipsChunk();
    	
    	// push two results
    	chunk.appendChunk(lhsResult);
    	chunk.appendChunk(rhsResult);
    	
    	// pop to registers
    	chunk.popToReg(Regs.t0);
    	chunk.popToReg(Regs.t1);
    	
    	// is the first one false?
    	chunk.append(mips.beqz(Regs.t0, oneFalse));
    	
    	// second one?
    	chunk.append(mips.beqz(Regs.t1, oneFalse));
    	
    	// push true
    	chunk.append(mips.li(Regs.t0, 1));
    	chunk.pushReg(Regs.t0);
    	
    	chunk.append(mips.j(endLabel));
    	chunk.append(mips.label(oneFalse));
    	
    	// push false
    	chunk.append(mips.li(Regs.t0, 0));
    	chunk.pushReg(Regs.t0);
    	
    	// the end label
    	chunk.append(mips.label(endLabel));
    	
        return chunk;
    }

    public MipsChunk visitOrOp(OrOp logOrOp, MipsChunk lhsResult, MipsChunk rhsResult)
    {
    	String oneTrue = falseLabel.genLabel();
    	String noneTrue = falseLabel.genLabel();
    	String endLabel = falseLabel.genLabel();
    	
    	MipsChunk chunk = new MipsChunk();
    	
    	// push two results
    	chunk.appendChunk(lhsResult);
    	chunk.appendChunk(rhsResult);
    	
    	// pop to registers
    	chunk.popToReg(Regs.t0);
    	chunk.popToReg(Regs.t1);
    	
    	// is the first one true?
    	chunk.append(mips.beq(Regs.t0, 1, oneTrue));
    	
    	// second one?
    	chunk.append(mips.beq(Regs.t1, 1, oneTrue));
    	
    	// push false
    	chunk.append(mips.li(Regs.t0, 0));
    	chunk.pushReg(Regs.t0);
    	
    	chunk.append(mips.j(endLabel));
    	chunk.append(mips.label(oneTrue));
    	
    	// push true
    	chunk.append(mips.li(Regs.t0, 1));
    	chunk.pushReg(Regs.t0);
    	
    	// the end label
    	chunk.append(mips.label(endLabel));
    	
        return chunk;
    }

    public MipsChunk visitAssignOp(AssignOp assignOp, MipsChunk lhsResult, MipsChunk rhsResult)
    {
    	MipsChunk chunk = new MipsChunk();
    	//Evaluate the right-hand-side expression, leaving the value on the stack.
    	chunk.appendChunk(rhsResult);
    	chunk.append(mips.lw(Regs.t0, 0, Regs.sp)); //value is in $t0, but not popped
    	//lhs is a pointer
    	if(assignOp.getLhsOperand().isPointer()) {
    		//DerefOp deref = (DerefOp)assignOp.getLhsOperand();
    		//Push the address of the left-hand-side Id onto the stack.       
    		chunk.appendChunk(lhsResult);           

    		//Store the value into the address.
    		chunk.popToReg(Regs.t2);
    		chunk.append(mips.lw(Regs.t3, (Regs.t2) ));
    		chunk.append(mips.sw(Regs.t0, (Regs.t3) ));

    		//Leave a copy of the value on the stack.
    	}
    	//lhs is a var ref
    	else {
    		//VarRefExp varRef = (VarRefExp)assignOp.getLhsOperand();           

    		//Push the address of the left-hand-side Id onto the stack.       
    		chunk.appendChunk(lhsResult);           

    		//Store the value into the address.
    		chunk.popToReg(Regs.t2);
    		chunk.append(mips.sw(Regs.t0, 0, (Regs.t2) ));

    		//Leave a copy of the value on the stack.
    	}
    	return chunk;
    }

    public MipsChunk visitEqualsOp(EqualsOp equalsOp, MipsChunk lhsResult, MipsChunk rhsResult)
    {
    	String trueLab = falseLabel.genLabel();
    	//String falseLab = falseLabel.genLabel();
    	String endLab = falseLabel.genLabel();
    	
    	// evaluate
    	MipsChunk chunk = new MipsChunk();
    	chunk.appendChunk(lhsResult);
    	chunk.appendChunk(rhsResult);
    	chunk.popToReg(Regs.t0);
    	chunk.popToReg(Regs.t1);
    	chunk.append(mips.beq(Regs.t0, Regs.t1, trueLab));
    	// false
    	//chunk.append(mips.label(falseLab));
    	chunk.append(mips.li(Regs.t0, 0));
    	chunk.pushReg(Regs.t0);
    	chunk.append(mips.label(endLab));
    	// true
    	chunk.append(mips.label(trueLab));
    	chunk.append(mips.li(Regs.t0, 1));
    	chunk.pushReg(Regs.t0);
    	// end
    	chunk.append(mips.label(endLab));
    	
        return chunk;
    }

    public MipsChunk visitNotEqualsOp(NotEqualsOp notEqualsOp,
                                    MipsChunk lhsResult, MipsChunk rhsResult)
    {
    	String trueLab = falseLabel.genLabel();
    	//String falseLab = falseLabel.genLabel();
    	String endLab = falseLabel.genLabel();
    	
    	// evaluate
    	MipsChunk chunk = new MipsChunk();
    	chunk.appendChunk(lhsResult);
    	chunk.appendChunk(rhsResult);
    	chunk.popToReg(Regs.t0);
    	chunk.popToReg(Regs.t1);
    	chunk.append(mips.bne(Regs.t0, Regs.t1, trueLab));
    	// false
    	//chunk.append(mips.label(falseLab));
    	chunk.append(mips.li(Regs.t0, 0));
    	chunk.pushReg(Regs.t0);
    	chunk.append(mips.label(endLab));
    	// true
    	chunk.append(mips.label(trueLab));
    	chunk.append(mips.li(Regs.t0, 1));
    	chunk.pushReg(Regs.t0);
    	// end
    	chunk.append(mips.label(endLab));
    	
        return chunk;
    }

    public MipsChunk visitLessThanOp(LessThanOp lessThanOp, MipsChunk lhsResult, MipsChunk rhsResult)
    {
    	String trueLab = falseLabel.genLabel();
    	//String falseLab = falseLabel.genLabel();
    	String endLab = falseLabel.genLabel();
    	
    	// evaluate
    	MipsChunk chunk = new MipsChunk();
    	chunk.appendChunk(lhsResult);
    	chunk.appendChunk(rhsResult);
    	chunk.popToReg(Regs.t0);
    	chunk.popToReg(Regs.t1);
    	chunk.append(mips.blt(Regs.t0, Regs.t1, trueLab));
    	// false
    	//chunk.append(mips.label(falseLab));
    	chunk.append(mips.li(Regs.t0, 0));
    	chunk.pushReg(Regs.t0);
    	chunk.append(mips.label(endLab));
    	// true
    	chunk.append(mips.label(trueLab));
    	chunk.append(mips.li(Regs.t0, 1));
    	chunk.pushReg(Regs.t0);
    	// end
    	chunk.append(mips.label(endLab));
    	
        return chunk;
    }

    public MipsChunk visitLessOrEqualOp(LessOrEqualOp lessOrEqualOp,
                                      MipsChunk lhsResult, MipsChunk rhsResult)
    {
    	String trueLab = falseLabel.genLabel();
    	//String falseLab = falseLabel.genLabel();
    	String endLab = falseLabel.genLabel();
    	
    	// evaluate
    	MipsChunk chunk = new MipsChunk();
    	chunk.appendChunk(lhsResult);
    	chunk.appendChunk(rhsResult);
    	chunk.popToReg(Regs.t0);
    	chunk.popToReg(Regs.t1);
    	chunk.append(mips.ble(Regs.t0, Regs.t1, trueLab));
    	// false
    	//chunk.append(mips.label(falseLab));
    	chunk.append(mips.li(Regs.t0, 0));
    	chunk.pushReg(Regs.t0);
    	chunk.append(mips.label(endLab));
    	// true
    	chunk.append(mips.label(trueLab));
    	chunk.append(mips.li(Regs.t0, 1));
    	chunk.pushReg(Regs.t0);
    	// end
    	chunk.append(mips.label(endLab));
    	
        return chunk;
    }

    public MipsChunk visitGreaterThanOp(GreaterThanOp greaterThanOp,
                                      MipsChunk lhsResult, MipsChunk rhsResult)
    {
    	String trueLab = falseLabel.genLabel();
    	//String falseLab = falseLabel.genLabel();
    	String endLab = falseLabel.genLabel();
    	
    	// evaluate
    	MipsChunk chunk = new MipsChunk();
    	chunk.appendChunk(lhsResult);
    	chunk.appendChunk(rhsResult);
    	chunk.popToReg(Regs.t0);
    	chunk.popToReg(Regs.t1);
    	chunk.append(mips.bgt(Regs.t0, Regs.t1, trueLab));
    	// false
    	//chunk.append(mips.label(falseLab));
    	chunk.append(mips.li(Regs.t0, 0));
    	chunk.pushReg(Regs.t0);
    	chunk.append(mips.label(endLab));
    	// true
    	chunk.append(mips.label(trueLab));
    	chunk.append(mips.li(Regs.t0, 1));
    	chunk.pushReg(Regs.t0);
    	// end
    	chunk.append(mips.label(endLab));
    	
        return chunk;
    }

    public MipsChunk visitGreaterOrEqualOp(GreaterOrEqualOp greaterOrEqualOp,
                                         MipsChunk lhsResult, MipsChunk rhsResult)
    {
    	String trueLab = falseLabel.genLabel();
    	//String falseLab = falseLabel.genLabel();
    	String endLab = falseLabel.genLabel();
    	
    	// evaluate
    	MipsChunk chunk = new MipsChunk();
    	chunk.appendChunk(lhsResult);
    	chunk.appendChunk(rhsResult);
    	chunk.popToReg(Regs.t0);
    	chunk.popToReg(Regs.t1);
    	chunk.append(mips.bge(Regs.t0, Regs.t1, trueLab));
    	// false
    	//chunk.append(mips.label(falseLab));
    	chunk.append(mips.li(Regs.t0, 0));
    	chunk.pushReg(Regs.t0);
    	chunk.append(mips.label(endLab));
    	// true
    	chunk.append(mips.label(trueLab));
    	chunk.append(mips.li(Regs.t0, 1));
    	chunk.pushReg(Regs.t0);
    	// end
    	chunk.append(mips.label(endLab));
    	
        return chunk;
    }

    public MipsChunk visitAddOp(AddOp addOp, MipsChunk lhsResult, MipsChunk rhsResult)
    {
    	MipsChunk chunk = new MipsChunk();

        // step 1: eval subexpressions
        chunk.appendChunk(lhsResult);
        chunk.appendChunk(rhsResult);

        // step 2: pop values in T0 and T1
        chunk.popToReg(Regs.t1);
        chunk.popToReg(Regs.t0);

        // step 3: do the addition (T0 = T0 + T1)
        chunk.append(mips.add(Regs.t0, Regs.t0, Regs.t1));

        // step 4: push result
        chunk.pushReg(Regs.t0);
        
        return chunk;
    }

    public MipsChunk visitSubtractOp(SubtractOp subtractOp, MipsChunk lhsResult, MipsChunk rhsResult)
    {
    	MipsChunk chunk = new MipsChunk();

        // step 1: eval subexpressions
        chunk.appendChunk(lhsResult);
        chunk.appendChunk(rhsResult);

        // step 2: pop values in T0 and T1
        chunk.popToReg(Regs.t1);
        chunk.popToReg(Regs.t0);

        // step 3: do the addition (T0 = T0 + T1)
        chunk.append(mips.sub(Regs.t0, Regs.t0, Regs.t1));

        // step 4: push result
        chunk.pushReg(Regs.t0);
        
        return chunk;
    }

    public MipsChunk visitMultiplyOp(MultiplyOp multiplyOp, MipsChunk lhsResult, MipsChunk rhsResult)
    {
    	MipsChunk chunk = new MipsChunk();

        // step 1: eval subexpressions
        chunk.appendChunk(lhsResult);
        chunk.appendChunk(rhsResult);

        // step 2: pop values in T0 and T1
        chunk.popToReg(Regs.t1);
        chunk.popToReg(Regs.t0);

        // step 3: do the addition (T0 = T0 + T1)
        chunk.append(mips.mul(Regs.t0, Regs.t0, Regs.t1));

        // step 4: push result
        chunk.pushReg(Regs.t0);
        
        return chunk;
    }

    public MipsChunk visitDivideOp(DivideOp divideOp, MipsChunk lhsResult, MipsChunk rhsResult)
    {
    	MipsChunk chunk = new MipsChunk();

        // step 1: eval subexpressions
        chunk.appendChunk(lhsResult);
        chunk.appendChunk(rhsResult);

        // step 2: pop values in T0 and T1
        chunk.popToReg(Regs.t1);
        chunk.popToReg(Regs.t0);

        // step 3: do the addition (T0 = T0 + T1)
        chunk.append(mips.div(Regs.t0, Regs.t0, Regs.t1));

        // step 4: push result
        chunk.pushReg(Regs.t0);
        
        return chunk;
    }

    public MipsChunk visitModOp(ModOp modOp, MipsChunk lhsResult, MipsChunk rhsResult)
    {
        return null;
    }

    public MipsChunk visitArrayAccessOp(ArrayAccessOp arrayAccess, MipsChunk array, MipsChunk index)
    {
    	MipsChunk chunk = new MipsChunk();
    	
    	
    	
        return chunk;
    }

    public MipsChunk visitNullLit(NullLit nullLit)
    {
    	MipsChunk chunk = new MipsChunk();
    	chunk.append(mips.li(Regs.t0, -1));
    	chunk.pushReg(Regs.t0);
        return chunk;
    }

    public MipsChunk visitBooleanLit(BooleanLit booleanLit)
    {
    	int i;
    	if (booleanLit.getValue()) {
    		i = 1;
    	} else {
    		i = 0;
    	}
    	MipsChunk chunk = new MipsChunk();
    	chunk.append(mips.li(Regs.t0, i));
    	chunk.pushReg(Regs.t0);
        return chunk;
    }

    public MipsChunk visitStringLit(StringLit stringLit)
    {
        return null;
    }

    public MipsChunk visitIntLit(IntLit intLit)
    {
    	MipsChunk chunk = new MipsChunk();
    	chunk.append(mips.li(Regs.t0, (int)intLit.getValue()));
    	chunk.pushReg(Regs.t0);
        return chunk;
    }

    public MipsChunk visitDoubleLit(DoubleLit doubleLit)
    {
        return null;
    }

    public MipsChunk visitVarRefExp(VarRefExp varRefExp)
    {    	
    	MipsChunk chunk = new MipsChunk();
    	
    	// perhaps a weird way of doing this, but whatever
    	String anString = varRefExp.getVariableSymbol().getAnnotationString();
    	if (anString.equals(":G")) {
    		// load word based on global label
    		chunk.append(mips.lw(Regs.t1, "_" + varRefExp.getName()));
    		chunk.pushReg(Regs.t1);
    	} else {
    		// load word based on offset of frame pointer
    		// we know we can cast because we're dealing with a local symbol
    		int offset = ((LocalSymbol) varRefExp.getVariableSymbol()).getOffset();
    		chunk.append(mips.lw(Regs.t1, offset, Regs.fp));
    		chunk.pushReg(Regs.t1);
    	}
    	
    	//chunk.append(mips.sub(Regs.sp, Regs.sp, varRefExp.getVariableSymbol().getType().getSize()));
    	//chunk.append(mips.sw(Regs.t1, 0, Regs.sp));
    	
    	
        return chunk;
    }

    public MipsChunk visitBooleanTypeNode(BooleanTypeNode boolType)
    {
        return null;
    }

    public MipsChunk visitIntTypeNode(IntTypeNode intType)
    {
        return null;
    }

    public MipsChunk visitDoubleTypeNode(DoubleTypeNode doubleType)
    {
        return null;
    }

    public MipsChunk visitStringTypeNode(StringTypeNode stringType)
    {
        return null;
    }

    public MipsChunk visitPointerTypeNode(PointerTypeNode pointerType, MipsChunk innerResult)
    {
        return null;
    }

    public MipsChunk visitArrayTypeNode(ArrayTypeNode pointerType, MipsChunk innerResult)
    {
        return null;
    }
}
